﻿//-----------------------------------------------------------------------
// <copyright file="ProductDataManager.cs" company="Streetlight Technologies L.L.C.">
//    Copyright Streetlight Technologies L.L.C. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
namespace Streetlight.Store.DataAccess
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Streetlight.Store.Contracts;

    /// <summary>
    /// Provides an instance of IProductManager using the Entity Framework to manage Product data.
    /// </summary>
    public class ProductDataManager : IProductManager
    {
        /// <summary>
        /// Instance of DataManager
        /// </summary>
        private DataManager _dataManager;

        /// <summary>
        /// Initializes a new instance of the <see cref="ProductDataManager"/> class.
        /// </summary>
        /// <param name="dataManager">DataManager instance containing this ProductDataManager instance.</param>
        public ProductDataManager(DataManager dataManager)
        {
            _dataManager = dataManager;
        }

        /// <summary>
        /// Returns a list of all products.
        /// </summary>
        /// <returns>New instance of List&lt;Product&gt; containing data for all products</returns>
        public List<Product> ListAll()
        {
            return _dataManager
                .Context
                .Products
                .ToList()
                .Select(p => p.CreateContract())
                .ToList();
        }

        /// <summary>
        /// Stores the provided Product as a new record.
        /// </summary>
        /// <param name="product">Product to be saved</param>
        public void CreateNew(Product product)
        {
            if (product == null)
            {
                throw new ArgumentNullException("product");
            }

            DataModels.Product productData = product.CreateEntity();

            _dataManager.Context.Products.Add(productData);

            _dataManager.Context.SaveChanges();

            product.Id = Convert.ToString(productData.ProductId);
        }

        /// <summary>
        /// Checks to see if a product exists with the specified item number and different
        /// ID.  This function can be used to ensure exclusivity of item numbers.  Returns true
        /// if a product exists with the specified item number and different ID, otherwise
        /// returns false.
        /// </summary>
        /// <param name="id">ID of item to validate item number for.  If parameter is null,
        /// empty, or whitespace, all existing items will be searched.</param>
        /// <param name="itemNumber">Item number to check for duplicates</param>
        /// <returns>True if a product exists with the specified product number and different ID, 
        /// otherwise false.</returns>
        public bool DuplicateItemNumberExists(string id, string itemNumber)
        {
            IQueryable<DataModels.Product> query = _dataManager.Context.Products.Where(p => p.ItemNumber == itemNumber);

            if (!string.IsNullOrWhiteSpace(id))
            {
                int idValue = ConversionHelper.TryParsePositiveInt("id", id);

                query = query.Where(p => p.ProductId != idValue);
            }

            return query.Any();
        }

        /// <summary>
        /// Checks to see if a product exists with the specified global ID and manufacturer and 
        /// different ID.  This function can be used to ensure exclusivity of Global ID/
        /// Manufacturer combinations.  Returns true if a product exists with the specified 
        /// global ID and manufacturer and different ID, otherwise returns false.
        /// </summary>
        /// <param name="id">ID of item to validate item number for.  If parameter is null,
        /// empty, or whitespace, all existing items will be searched.</param>
        /// <param name="globalId">Global ID to find duplicates for</param>
        /// <param name="manufacturer">Manufacturer to find duplicates for</param>
        /// <returns>True if a product exists with the specified 
        /// global ID and manufacturer and different ID, otherwise false.</returns>
        public bool DuplicateGlobalIdManufacturerExists(string id, string globalId, string manufacturer)
        {
            IQueryable<DataModels.Product> query = _dataManager.Context.Products.Where(p => p.GlobalId == globalId && p.Manufacturer == manufacturer);

            if (!string.IsNullOrWhiteSpace(id))
            {
                int idValue = ConversionHelper.TryParsePositiveInt("id", id);

                query = query.Where(p => p.ProductId != idValue);
            }

            return query.Any();
        }

        /// <summary>
        /// Gets the product with the specified id.
        /// </summary>
        /// <param name="id">ID of product to retrieve</param>
        /// <returns>New instance of Product with data for provided ID.</returns>
        public Product GetProduct(string id)
        {
            int idValue;

            if (!int.TryParse(id, out idValue))
            {
                throw new InvalidOperationException("Parameer \"id\" must evaluate to an integer.");
            }

            DataAccess.DataModels.Product productData = _dataManager.Context.Products.FirstOrDefault(p => p.ProductId == idValue);

            if (productData == null)
            {
                return null;
            }
            else
            {
                return productData.CreateContract();
            }
        }

        /// <summary>
        /// Saves the provided Product as an existing record.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown when no product is found for the ID
        /// provided as product.Id.</exception>
        /// <param name="product">Product to save</param>
        public void Save(Product product)
        {
            if (product == null)
            {
                throw new ArgumentNullException("product");
            }

            int id;

            if (!int.TryParse(product.Id, out id))
            {
                throw new InvalidOperationException("Parameer \"id\" must evaluate to an integer.");
            }

            DataModels.Product productData = _dataManager.Context.Products.FirstOrDefault(p => p.ProductId == id);

            if (productData == null)
            {
                throw new InvalidOperationException(string.Format("Product not found for id {0}.", id));
            }

            product.CopyToEntity(productData);

            _dataManager.Context.SaveChanges();

            productData.CopyToContract(product);
        }

        /// <summary>
        /// Returns a list of all categories.
        /// </summary>
        /// <returns>New instance of List&lt;ProductCategory&gt; containing data for all categories</returns>
        public List<ProductCategory> ListAllCategories()
        {
            return _dataManager
                .Context
                .ProductCategories
                .ToList()
                .Select(c => new ProductCategory
                    {
                        Id = Convert.ToString(c.ProductCategoryId),
                        Name = c.Name,
                        Description = c.Description
                    })
                .ToList();
        }

        /// <summary>
        /// Stores the category provided as a new record.
        /// </summary>
        /// <param name="category">Category to be saved</param>
        public void CreateNewCategory(ProductCategory category)
        {
            if (category == null)
            {
                throw new ArgumentNullException("category");
            }

            DataModels.ProductCategory categoryData = new DataModels.ProductCategory 
            { 
                Name = category.Name, 
                Description = category.Description
            };

            _dataManager.Context.ProductCategories.Add(categoryData);
            
            _dataManager.Context.SaveChanges();

            category.Id = Convert.ToString(categoryData.ProductCategoryId);
        }

        /// <summary>
        /// Gets the category with the specified ID.
        /// </summary>
        /// <param name="id">ID of category to return</param>
        /// <returns>New instance of ProductCategory with data for provided ID.</returns>
        public ProductCategory GetCategory(string id)
        {
            int idValue = ConversionHelper.TryParsePositiveInt("id", id);

            DataModels.ProductCategory categoryData = _dataManager
                .Context
                .ProductCategories
                .FirstOrDefault(c => c.ProductCategoryId == idValue);

            if (categoryData == null)
            {
                return null;
            }

            return new ProductCategory { Id = id, Name = categoryData.Name, Description = categoryData.Description };
        }

        /// <summary>
        /// Saves the provided category as an existing recod.
        /// </summary>
        /// <exception cref="System.InvalidOperationException">Thrown when no caetgory is found for the ID
        /// provided as category.Id.</exception>
        /// <param name="category">Category to save</param>
        public void SaveCategory(ProductCategory category)
        {
            if (category == null)
            {
                throw new ArgumentNullException("category");
            }

            int id = ConversionHelper.TryParsePositiveInt("category.Id", category.Id);

            DataModels.ProductCategory categoryData = _dataManager.Context.ProductCategories.FirstOrDefault(c => c.ProductCategoryId == id);

            if (category == null)
            {
                throw new InvalidOperationException(string.Format("Category not found for id {0}.", id));
            }

            categoryData.Name = category.Name;
            category.Description = category.Description;
            
            _dataManager.Context.SaveChanges();
        }

        /// <summary>
        /// Lists categories associated with the specified ID.
        /// </summary>
        /// <param name="id">ID of product to list categories for</param>
        /// <returns>New instance of List&lt;ProductCategory&gt; containing categories associated with product.</returns>
        public List<ProductCategory> ListCategoriesForProduct(string id)
        {
            int idValue = ConversionHelper.TryParsePositiveInt("id", id);

            DataModels.Product productData = _dataManager
                .Context
                .Products
                .Include("ProductCategories")
                .FirstOrDefault(p => p.ProductId == idValue);

            if (productData == null)
            {
                throw new InvalidOperationException(string.Format("Product not found for id {0}.", idValue));
            }

            return productData
                .ProductCategories
                .Select(c => new ProductCategory { Id = Convert.ToString(c.ProductCategoryId), Name = c.Name, Description = c.Description })
                .ToList();
        }

        /// <summary>
        /// Adds the product with the specified ID to the category with the specified ID.  If a relationship
        /// already exists, nothing is done.
        /// </summary>
        /// <param name="productId">ID of product to add</param>
        /// <param name="categoryId">ID of category to add to</param>
        public void AddProductToCategory(string productId, string categoryId)
        {
            int productIdValue = ConversionHelper.TryParsePositiveInt("productId", productId);

            int categoryIdValue = ConversionHelper.TryParsePositiveInt("categoryId", categoryId);

            DataModels.ProductCategory category = _dataManager
                .Context
                .ProductCategories
                .Include("Products")
                .FirstOrDefault(c => c.ProductCategoryId == categoryIdValue);

            if (category == null)
            {
                throw new InvalidOperationException(string.Format("Category not found for id {0}.", categoryIdValue));
            }

            if (category.Products.Any(p => p.ProductId == productIdValue))
            {
                return;
            }

            DataModels.Product product = _dataManager
                .Context
                .Products
                .FirstOrDefault(p => p.ProductId == productIdValue);

            if (product == null)
            {
                throw new InvalidOperationException(string.Format("Product not found for id {0}.", productIdValue));
            }

            category.Products.Add(product);

            _dataManager.Context.SaveChanges();
        }

        /// <summary>
        /// Removes the product with the specified ID from the category with the specified ID.  If no relationship
        /// exists, nothing is done.
        /// </summary>
        /// <param name="productId">ID of product to remove</param>
        /// <param name="categoryId">ID of category to remove from</param>
        public void RemoveProductFromCategory(string productId, string categoryId)
        {
            int productIdValue = ConversionHelper.TryParsePositiveInt("productId", productId);

            int categoryIdValue = ConversionHelper.TryParsePositiveInt("categoryId", categoryId);

            DataModels.ProductCategory category = _dataManager
                .Context
                .ProductCategories
                .Include("Products")
                .FirstOrDefault(c => c.ProductCategoryId == categoryIdValue);

            if (category == null)
            {
                throw new InvalidOperationException(string.Format("Category not found for id {0}.", categoryIdValue));
            }

            if (!category.Products.Any(p => p.ProductId == productIdValue))
            {
                return;
            }

            DataModels.Product product = _dataManager
                .Context
                .Products
                .FirstOrDefault(p => p.ProductId == productIdValue);

            if (product == null)
            {
                throw new InvalidOperationException(string.Format("Product not found for id {0}.", productIdValue));
            }

            category.Products.Remove(product);

            _dataManager.Context.SaveChanges();
        }
    }
}
